//
// Manta - Structural Variant and Indel Caller
// Copyright (c) 2013-2019 Illumina, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//

/// \file
/// \author Chris Saunders
///

#include "GSCOptions.hpp"

#include "EdgeOptionsParser.hpp"
#include "blt_util/log.hpp"
#include "common/ProgramUtil.hpp"
#include "options/AlignmentFileOptionsParser.hpp"
#include "options/ReadScannerOptionsParser.hpp"
#include "options/optionsUtil.hpp"

#include "boost/program_options.hpp"

static void usage(
    std::ostream&                                      os,
    const illumina::Program&                           prog,
    const boost::program_options::options_description& visible,
    const char*                                        msg = nullptr)
{
  usage(os, prog, visible, "call candidates from an SV Locus graph", "", msg);
}

static void checkStandardizeUsageFile(
    std::ostream&                                      os,
    const illumina::Program&                           prog,
    const boost::program_options::options_description& visible,
    std::string&                                       filename,
    const char*                                        fileLabel)
{
  std::string errorMsg;
  if (checkAndStandardizeRequiredInputFilePath(filename, fileLabel, errorMsg)) {
    usage(os, prog, visible, errorMsg.c_str());
  }
}

void parseGSCOptions(const illumina::Program& prog, int argc, char* argv[], GSCOptions& opt)
{
  namespace po = boost::program_options;
  po::options_description req("configuration");
  // clang-format off
  req.add_options()
  ("threads", po::value(&opt.workerThreadCount)->default_value(opt.workerThreadCount),
   "Number of threads to use for candidate generation")
  ("graph-file", po::value(&opt.graphFilename),
   "sv locus graph file (required)")
  ("align-stats", po::value(&opt.statsFilename),
   "pre-computed alignment statistics for the input alignment files (required)")
  ("chrom-depth", po::value(&opt.chromDepthFilename),
   "average depth estimate for each chromosome")
  ("ref", po::value(&opt.referenceFilename),
   "fasta reference sequence (required)")
  ("edge-runtime-log", po::value(&opt.edgeRuntimeFilename),
   "optionally log time for long-running edges to this file")
  ("edge-stats-log", po::value(&opt.edgeStatsFilename),
   "optionally log aggregate edge statistics to this file")
  ("edge-stats-report", po::value(&opt.edgeStatsReportFilename),
   "optionally generate a report from aggregate edge statistics to this file")
  ("candidate-output-file", po::value(&opt.candidateOutputFilename),
   "Write SV candidates to file (required)")
  ("diploid-output-file", po::value(&opt.diploidOutputFilename),
   "Write germline diploid SVs to file (at least one non-tumor alignment file must be specified)")
  ("somatic-output-file", po::value(&opt.somaticOutputFilename),
   "Write somatic SVs to file (at least one tumor and non-tumor alignment file must be specified)")
  ("tumor-output-file", po::value(&opt.tumorOutputFilename),
   "Write tumor SVs to file (at least one tumor alignment file must be specified)")
  ("rna-output-file", po::value(&opt.rnaOutputFilename),
   "Write RNA fusions to file (at least one BAM file and --rna must be specified)")
  ("verbose", po::value(&opt.isVerbose)->zero_tokens(),
   "Turn on low-detail INFO logging.")
  ("skip-assembly", po::value(&opt.isSkipAssembly)->zero_tokens(),
   "Turn off all breakend and small-variant assembly. Only large, imprecise variants will be reported based on anomalous read pairs.")
  ("skip-scoring", po::value(&opt.isSkipScoring)->zero_tokens(),
   "Turn off all scoring models and output candidates only.")
  ("enable-remote-read-retrieval", po::value(&opt.enableRemoteReadRetrieval)->zero_tokens(),
   "Turn on retrieval of poorly mapped remote reads for assembly (improves assembly success for insertions, but may cause runtime issues in noisy data).")
  ("rna", po::value(&opt.isRNA)->zero_tokens(),
   "For RNA input. Skip small deletions and modify diploid scoring.")
  ("unstranded", po::value(&opt.isUnstrandedRNA)->zero_tokens(),
   "For RNA input. Is data stranded?.")
  ("min-candidate-spanning-count", po::value(&opt.minCandidateSpanningCount)->default_value(opt.minCandidateSpanningCount),
   "minimum number of supporting spanning observations required to become an SV candidate")
  ("min-scored-sv-size", po::value(&opt.minScoredVariantSize)->default_value(opt.minScoredVariantSize),
   "minimum size for variants which are scored and output following initial candidate generation")
  ("evidence-bam-stub", po::value(&opt.evidenceBamStub)->default_value(opt.evidenceBamStub),
   "Directory and prefix of bams storing the supporting reads of SVs")
  ("output-contigs", po::value(&opt.isOutputContig)->zero_tokens(),
   "Output assembled contig sequences in VCF files.")
  ("skip-evidence-signal-filter", po::value(&opt.skipEvidenceSignalFilter)->zero_tokens(),
   "Turn off the filter on candidates of insignificant evidence signal.")
  ;
  // clang-format on

  po::options_description alignDesc(getOptionsDescription(opt.alignFileOpt));
  po::options_description edgeDesc(getOptionsDescription(opt.edgeOpt));
  po::options_description scanDesc(getOptionsDescription(opt.scanOpt));
  po::options_description diploidCallDesc(getOptionsDescription(opt.diploidOpt));
  po::options_description somaticCallDesc(getOptionsDescription(opt.somaticOpt));
  po::options_description tumorCallDesc(getOptionsDescription(opt.tumorOpt));

  po::options_description help("help");
  help.add_options()("help,h", "print this message");

  po::options_description visible("options");
  visible.add(alignDesc)
      .add(scanDesc)
      .add(req)
      .add(edgeDesc)
      .add(diploidCallDesc)
      .add(somaticCallDesc)
      .add(tumorCallDesc)
      .add(help);

  bool              po_parse_fail(false);
  po::variables_map vm;
  try {
    po::store(
        po::parse_command_line(
            argc, argv, visible, po::command_line_style::unix_style ^ po::command_line_style::allow_short),
        vm);
    po::notify(vm);
  } catch (const boost::program_options::error&
               e)  // todo:: find out what is the more specific exception class thrown by program options
  {
    log_os << "\nERROR: Exception thrown by option parser: " << e.what() << "\n";
    po_parse_fail = true;
  }

  if ((argc <= 1) || (vm.count("help")) || po_parse_fail) {
    usage(log_os, prog, visible);
  }

  std::string errorMsg;
  if (parseOptions(vm, opt.edgeOpt, errorMsg)) {
  } else if (parseOptions(vm, opt.alignFileOpt, errorMsg)) {
  } else if (parseOptions(vm, opt.scanOpt, errorMsg)) {
  } else if (opt.statsFilename.empty()) {
    errorMsg = "Need the alignment stats file";
  } else if (opt.referenceFilename.empty()) {
    errorMsg = "Need the FASTA reference file";
  }

  if (!errorMsg.empty()) usage(log_os, prog, visible, errorMsg.c_str());

  checkStandardizeUsageFile(log_os, prog, visible, opt.graphFilename, "SV locus graph");
  checkStandardizeUsageFile(log_os, prog, visible, opt.referenceFilename, "reference fasta");
  checkStandardizeUsageFile(log_os, prog, visible, opt.statsFilename, "alignment statistics");

  if (!opt.chromDepthFilename.empty()) {
    checkStandardizeUsageFile(log_os, prog, visible, opt.chromDepthFilename, "chromosome depth");
  }
  if (opt.candidateOutputFilename.empty()) {
    usage(log_os, prog, visible, "Must specify candidate output file");
  }

  {
    unsigned normalCount(0);
    unsigned tumorCount(0);
    for (const bool value : opt.alignFileOpt.isAlignmentTumor) {
      if (value)
        tumorCount++;
      else
        normalCount++;
    }

    /*if (opt.diploidOutputFilename.empty())
    {
            usage(log_os,prog,visible,"Must specify diploid output file");
    }*/

    if (!opt.diploidOutputFilename.empty()) {
      if (normalCount == 0) {
        usage(log_os, prog, visible, "Must specify at least one non-tumor alignment file for diploid output");
      }
    }

    if (!opt.somaticOutputFilename.empty()) {
      if ((normalCount == 0) || (tumorCount == 0)) {
        usage(
            log_os,
            prog,
            visible,
            "Must specify at least one tumor and non-tumor alignment file for somatic output");
      }
    }

    if (!opt.tumorOutputFilename.empty()) {
      if (tumorCount == 0) {
        usage(log_os, prog, visible, "Must specify at least one tumor alignment file for tumor output");
      }
    }

    if ((!opt.rnaOutputFilename.empty()) != opt.isRNA) {
      usage(log_os, prog, visible, "For RNA, specify RNA output file and --rna");
    }
  }
}
